#ifndef CONDITION_VARIABLE_HEADER_H
#define CONDITION_VARIABLE_HEADER_H

#include "xtime.hpp"
#include "NonCopyable.hpp"
#include "Mutex.hpp"
#include <cassert>

class ConditionVariable : private NonCopyable {

public:
    bool create();
    bool close();

    bool wait(Mutex& lock);
    bool timedwait(Mutex& lock, const xtime& timeout);
    bool signal();
    bool broadcast();

private:

    HANDLE _gate;
    HANDLE _queue;
    HANDLE _mutex;
    unsigned _gone;
    unsigned long _blocked;
    unsigned _waiting;
};

bool 
ConditionVariable::create()
{
    _gone        = 0;
    _blocked    = 0;
    _waiting    = 0;
    _gate        = ::CreateSemaphore(0, 1, 1, 0);
    _queue        = ::CreateSemaphore(0, 0, LONG_MAX, 0);
    _mutex        = ::CreateMutex(0, 0, 0);
    return true;
}

bool 
ConditionVariable::close()
{
    ::CloseHandle(_gate);
    ::CloseHandle(_queue);
    ::CloseHandle(_mutex);
    return true;
}

bool 
ConditionVariable::wait(Mutex& lock)
{
    ::WaitForSingleObject(_gate, INFINITE);
    _blocked++;
    ::ReleaseSemaphore(_gate, 1, 0);

    lock.unlock();

    int res = 0;
    res = ::WaitForSingleObject(_queue, INFINITE);
    assert(res == WAIT_OBJECT_0);

    unsigned was_waiting=0;
    unsigned was_gone=0;

    res = ::WaitForSingleObject(_mutex, INFINITE);
    assert(res == WAIT_OBJECT_0);
    was_waiting = _waiting;
    was_gone = _gone;
    if (was_waiting != 0) {
        if (--_waiting == 0) {
            if (_blocked != 0) {
                res = ::ReleaseSemaphore(_gate, 1, 0); // open _gate
                assert(res);
                was_waiting = 0;
            }
            else if (_gone != 0)
                _gone = 0;
        }
    }
    else if (++_gone == (UINT_MAX / 2)) {
        res = ::WaitForSingleObject(_gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        _blocked -= _gone;
        res = ::ReleaseSemaphore(_gate, 1, 0);
        assert(res);
        _gone = 0;
    }
    res = ::ReleaseMutex(_mutex);
    assert(res);

    if (was_waiting == 1) {
        for (; was_gone; --was_gone) {
            // better now than spurious later
            res = ::WaitForSingleObject(_queue, INFINITE);
            assert(res == WAIT_OBJECT_0);
        }
        res = ::ReleaseSemaphore(_gate, 1, 0);
        assert(res);
    }

    lock.lock();
    return true;
}

bool 
ConditionVariable::timedwait(Mutex& lock, const xtime& timeout)
{
    ::WaitForSingleObject(_gate, INFINITE);
    _blocked++;
    ::ReleaseSemaphore(_gate, 1, 0);

    lock.unlock();

    bool ret = false;
    unsigned int res = 0;

    for (;;) {
        int milliseconds;
        to_duration(timeout, milliseconds);

        res = ::WaitForSingleObject(_queue, milliseconds);
        assert(res != WAIT_FAILED && res != WAIT_ABANDONED);
        ret = (res == WAIT_OBJECT_0);

        if (res == WAIT_TIMEOUT) {
            xtime cur;
            xtime::now(cur);
            if (xtime_cmp(timeout, cur) > 0)
                continue;
        }

        break;
    }

    unsigned was_waiting=0;
    unsigned was_gone=0;

    res = ::WaitForSingleObject(_mutex, INFINITE);
    assert(res == WAIT_OBJECT_0);
    was_waiting = _waiting;
    was_gone = _gone;
    if (was_waiting != 0) {
        if (!ret) { // timeout
            if (_blocked != 0)
                --_blocked;
            else
                ++_gone; // count spurious wakeups
        }
        if (--_waiting == 0) {
            if (_blocked != 0) {
                res = ::ReleaseSemaphore(_gate, 1, 0); // open m_gate
                assert(res);
                was_waiting = 0;
            }
            else if (_gone != 0)
                _gone = 0;
        }
    }
    else if (++_gone == (UINT_MAX) / 2) {
        res = ::WaitForSingleObject(_gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        _blocked -= _gone;
        res = ::ReleaseSemaphore(_gate, 1, 0);
        assert(res);
        _gone = 0;
    }
    res = ::ReleaseMutex(_mutex);
    assert(res);

    if (was_waiting == 1) {
        for (; was_gone; --was_gone) {
            // better now than spurious later
            res = ::WaitForSingleObject(_queue, INFINITE);
            assert(res ==  WAIT_OBJECT_0);
        }
        res = ::ReleaseSemaphore(_gate, 1, 0);
        assert(res);
    }
    lock.lock();
    return ret;
}

bool 
ConditionVariable::signal()
{
    unsigned signals = 0;
    int res = 0;

    res = ::WaitForSingleObject(_mutex, INFINITE);
    assert(res == WAIT_OBJECT_0);

    if (_waiting != 0) { // the m_gate is already closed
        if (_blocked == 0) {
            res = ::ReleaseMutex(_mutex);
            assert(res);
            return true; // xupei
        }

        ++_waiting;
        --_blocked;
        signals = 1;
    }
    else
    {
        res = ::WaitForSingleObject(_gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        if (_blocked > _gone) {
            if (_gone != 0) {
                _blocked -= _gone;
                _gone = 0;
            }
            signals = _waiting = 1;
            --_blocked;
        }
        else {
            res = ::ReleaseSemaphore(_gate, 1, 0);
            assert(res);
        }
    }

    res = ::ReleaseMutex(_mutex);
    assert(res);

    if (signals) {
        res = ::ReleaseSemaphore(_queue, signals, 0);
        assert(res);
    }
    return true;
}

bool 
ConditionVariable::broadcast()
{
    unsigned signals = 0;

    int res = 0;
    res = ::WaitForSingleObject(_mutex, INFINITE);
    assert(res == WAIT_OBJECT_0);

    if (_waiting != 0) { // the m_gate is already closed
        if (_blocked == 0) {
            res = ::ReleaseMutex(_mutex);
            assert(res);
            return true; // xupei
        }
        _waiting += (signals = _blocked);
        _blocked = 0;
    }
    else {
        res = ::WaitForSingleObject(_gate, INFINITE);
        assert(res == WAIT_OBJECT_0);
        if (_blocked > _gone) {
            if (_gone != 0) {
                _blocked -= _gone;
                _gone = 0;
            }
            signals = _waiting = _blocked;
            _blocked = 0;
        }
        else {
            res = ::ReleaseSemaphore(_gate, 1, 0);
            assert(res);
        }
    }
    res = ::ReleaseMutex(_mutex);
    assert(res);

    if (signals) {
        res = ::ReleaseSemaphore(_queue, signals, 0);
        assert(res);
    }
    return true;
}


#endif